<?php
/**
 * Copyright 2013 Andrey Tushev and Probasis 
 */

namespace Emos\Editor;

use Emos\Field;
use Emos\ListField;
use Emos\Common;


class Entity {
    var $name = null;
    
    var $form;
    var $columns;
    var $entities = array();
    
    var $table;
    protected $orderBy = null;
    protected $where = "1=1";    
    var $pk;
    var $fkes = array();
    var $moveField = null;
    
    protected $defaultRecord = array();

    protected $showGrid = null;
    
    var $tree;
    
    protected $title = null;
    
    protected $inited = false;
    
    public $allowInsert = true, $allowUpdate = true, $allowDelete = true;
    
    public $fakeRecordsBatchSize = 5;

    protected $limit = null;

    public function allowActions($allowInsert=true, $allowUpdate=true, $allowDelete=true) {
        $this->allowInsert = $allowInsert;
        $this->allowUpdate = $allowUpdate; 
        $this->allowDelete = $allowDelete;
    }
    
    public function setLimit($limit) {
        $this->limit = $limit;
    }

    public function setMoveField($fieldName) {
        $this->moveField = $fieldName;
    }

    public function getMoveField() {
        return $this->moveField;
    }    
    
    public function setTitle($title) {
        $this->title = $title;
    }
    
    public function getTitle() {
        return $this->title;
    }    

    public function getWhere() {
        return $this->where;
    }        
    
    public function addEntity($entity, $fkName) {
        $this->entities[] = array(
            'entity' => $entity,
            'fkName' => $fkName
        );
    }
            
    public function open($pkValue) {        
        $treeName = $this->tree->name;
        $_SESSION['emos'][$treeName]['openedBranches'][$this->name][$pkValue] = true;
    }    
    
    public function close($pkValue) {        
        $treeName = $this->tree->name;
        unset($_SESSION['emos'][$treeName]['openedBranches'][$this->name][$pkValue]);
    }     
    
    public function isOpen($pkValue) {                
        $treeName = $this->tree->name;
        return !!@$_SESSION['emos'][$treeName]['openedBranches'][$this->name][$pkValue];
    }    
    
    
    function addColumn($dbName, $label = null, ListField\AbstractListField $listField = null) {
        static $count = 0;
        if($dbName === null) {            
            $dbName = "__unnamed_{$count}";
            $count++;
        }
        
        if($listField === null) {
            $listField = new ListField\String();
        }
        
        $listField->dbName = $dbName;
        $listField->label  = $label;
        
        $this->columns[$dbName] = $listField;
        
        return $listField;
    }
    
    public function addField($name, $label, $field) {
        $this->form->elementSet->add($name, $label, $field);
        return $field;
    }
    
    
    function __construct($table, $pk='pk') {
        $this->table = $table;
        $this->pk = $pk;                
        
        $this->form = new Form();        
    }
    
    public function setWhere($where) {
        $this->where = $where;
    }
 
    public function setOrderBy($orderBy) {
        $this->orderBy = $orderBy;
    }    
    
    public function setDefaultRecord(array $defaultRecord) {
        $this->defaultRecord = $defaultRecord;
    }     
    
    public function setGrid($showGrid = true) {
        $this->showGrid = $showGrid;
    }
    
    function init() {
        if($this->inited) {
            return;
        }
        $this->inited = true;
                
        if($this->orderBy === null) {
            $this->orderBy = $this->tree->db->nameEscape($this->pk);
        }
        
        if($this->name === null) {
            $this->name = $this->table.'_Entity';
        }                
        
        if($this->title === null) { 
            $this->title = $this->table;
        }
        
        // Adds sort fields if nessesary
        if($this->moveField) {
            $this->addField($this->moveField, null, new Field\Sort());
            $this->setOrderBy($this->tree->db->nameEscape($this->moveField));
        }
        

        
        $this->form->init();

        
        $controls = $this->form->elementSet->getControls();
        // Add columns by default (icon + first field)
        if(count($this->columns) == 0) {                        
            reset($controls);
            $fistControl = current($controls);
                        
            if($fistControl) {
                $iconSrc = '/emos/images/' . ((count($this->entities) > 0) ? 'folder_icon.png' : 'item_icon.png');
                $this->addColumn('__icon', null, new ListField\Icon($iconSrc));
                $this->addColumn($fistControl->dbName);                
            }
        }        
        
        foreach($controls as $control) {
            $control->db = $this->tree->db;
        }
        
        // Do we need to show grid?
        if($this->showGrid === null) {
            $needBorderCnt = 0;
            foreach($this->columns as $listFied) {
                $needBorderCnt += $listFied->needBorder;
            }    
            if($needBorderCnt > 1) {
                $this->setGrid(true);
            }
        }     
        
        
        $this->tree->entitiesDict[$this->name] = $this;
        
        foreach ($this->entities as $e) {   
            $entity = $e['entity'];
            $fkName = $e['fkName'];
            $entity->tree = $this->tree;
            $entity->fkes[$fkName] = $fkName;
            $entity->init();            
        }             
    }
        
    public function getParentItem($pkValue) {               
        foreach($this->tree->entitiesDict as $parentEntity) {
            foreach($parentEntity->entities as $e) {
                if($e['entity'] == $this) {                                        
                    $row = $this->tree->db->selectRow("
                        SELECT p.*, t.?fkName:name emos_fk_value
                        FROM ?table:name t
                        INNER JOIN ?parentTable:name p ON t.?fkName:name = p.?parentPkName:name
                        WHERE t.?pkName:name = ?pkValue
                    ", array(
                        'parentTable'   => $parentEntity->table,
                        'parentPkName'  => $parentEntity->pk,                        
                        'table'         => $this->table,
                        'pkName'        => $this->pk,
                        'pkValue'       => $pkValue,
                        'fkName'        => $e['fkName']
                    ));   
                    if($row) {
                        $parentPkValue = $row['emos_fk_value'];
                        unset($row['emos_fk_value']);                    
                        return array('entity'=>$parentEntity, 'pkValue'=>$parentPkValue, 'row'=>$row, 'childFkName'=>$e['fkName']); 
                    }
                }            
            }            
        }
        return null;        
    }    

            
    public function getListHtml($fkName=null, $fkValue=null) {         
        $otherFkes = $this->fkes;
        if($fkName !== null && $fkValue !== null) {
            unset($otherFkes[$fkName]);
        }
        $otherFkesSql = '1=1';
        foreach($otherFkes as $otherFk) {            
            $otherFkesSql .= " AND ";                        
            $otherFkesSql .= "(".$this->tree->db->nameEscape($otherFk)."=0 OR ".$this->tree->db->nameEscape($otherFk)." IS NULL)";
        }                
        
        $limitParams = array();
        if($this->limit > 0) {
            $limitParams = array('limit'=>$this->limit);
        }
        
        if($fkName !== null && $fkValue !== null) {                        
            $data = $this->tree->db->select("SELECT * FROM ?table:name WHERE ?where:raw AND ?fkName:name = ?fkValue AND ?otherFkesSql:raw ORDER BY ?orderBy:raw", null, array(
                'table' => $this->table,
                'where' => $this->where,
                'orderBy' => $this->orderBy,                
                'fkName' => $fkName,
                'fkValue' => $fkValue,
                'otherFkesSql' => $otherFkesSql,
                'limit' => $this->limit
            )+$limitParams);            
        }
        else {
            $data = $this->tree->db->select("SELECT * FROM ?table:name WHERE ?where:raw AND ?otherFkesSql:raw ORDER BY ?orderBy:raw", null, array(
                'table' => $this->table,
                'where' => $this->where,
                'orderBy' => $this->orderBy,
                'otherFkesSql' => $otherFkesSql,
                'limit' => $this->limit
            )+$limitParams);
        }
        
        if(!$data) {
            return '';
        }
        
        
        $currentNode = $this->tree->getCurrentNode();
        
        ob_start();        
       
        $gridClass = $this->showGrid ? 'showGrid' : 'noGrid';
        
        print "<table class='items {$gridClass}' data-entity='{$this->name}'>";
        
        foreach ($this->columns as $listField) {
            // TODO: Col Headers
        }                
        
        foreach ($data as $pk=>$rec) {
            $pkValue = $rec[$this->pk];
            
            $trClass = 'record';
            if($currentNode && ($currentNode->getEntityName() == $this->name) && ($currentNode->getPkValue() == $pkValue) && ($currentNode->isSub() === false)) {
                $trClass .= " selected";
            }            
            print "<tr class='{$trClass}' data-pk='".Common::plain($pkValue)."'>";
            
            if(count($this->entities) > 0) {
                if($this->isOpen($pkValue)) {
                    print "<td class='oc close'> </td>";                                
                }
                else {
                    print "<td class='oc open'> </td>";                
                }            
            }
            else {
                print "<td class='oc'> </td>";
            }
            
            foreach ($this->columns as $listField) {                
                $listField->setValueFromRecord($rec);
                
                $stylesCode = '';
                $styles = $listField->getContainerStyles();
                if($styles) {
                    $stylesCode = " style='";
                    foreach ($styles as $name=>$value) {
                        $stylesCode .= "{$name}:{$value};";
                    }
                    $stylesCode .= "'";
                }
                
                $widthCode = '';
                $width = $listField->width;
                if($width) {
                    $widthCode = " width='{$width}'";
                }
                
                print "<td{$stylesCode}{$widthCode}>".$listField->getHtml()."</td>";                
            }
            print "</tr>";
            
            if($this->isOpen($pkValue)) {
                print 
                    "<tr data-pk='".Common::plain($pkValue)."'>".
                    "<td colspan='".(count($this->columns)+1)."' class='sublevel'>";
                
                $listHtml = '';
                foreach($this->entities as $e) {
                    $entity = $e['entity'];
                    $fkName = $e['fkName'];
                    $listHtml .= $entity->getListHtml($fkName, $pkValue);
                }
                
                if($listHtml === '') {
                    $class = 'empty';
                    if($currentNode && ($currentNode->getEntityName() == $this->name) && ($currentNode->getPkValue() == $pkValue) && ($currentNode->isSub() === true)) {
                        $class .= " selected";
                    }                                
                    print "<div class='{$class}'>".Common::t('empty')."</div>";
                }
                else {
                    print $listHtml;
                }
                
                print 
                    "</td>".
                    "</tr>";                
            }
        }
        print "</table>";
        
        return ob_get_clean();
    }
    
    public function getFormHtml($pkValue=null, Node $parentNode=null) {                              
        if($pkValue === null) {            
            $this->form->setValuesFromRecord($this->defaultRecord);           
        }
        else {
            $record = $this->tree->db->selectRow("SELECT * FROM ?table:name WHERE ?pkName:name = ?pkValue:int", array(
                'table'   => $this->table,
                'pkName'  => $this->pk,
                'pkValue' => $pkValue)
            );            
            $this->form->setValuesFromRecord($record);           
        }
        
        $hf = array();
        
        // New record (for INSERT)
        if($pkValue === null) { 
            $hf['action'] = 'insert';     
            if($parentNode === null) {
                $hf['parentEntityName'] = null;                                
                $hf['parentPkValue'] = null;
            }
            else {                
                $hf['parentEntityName'] = $parentNode->getEntityName();                                
                $hf['parentPkValue'] = $parentNode->getPkValue();                                
            }
            $hf['newEntityName'] = $this->name;                                
        }
        // Existant record (for UPDATE)
        else { 
            $hf['action'] = 'update';        
            $hf['entityName'] = $this->name;
            $hf['pkValue'] = $pkValue;            
        }        
        $this->form->setHiddenFields($hf);
        
        $html  = "<form action='' method='post' enctype='multipart/form-data' class='form'>";                
        $html .= "<div class='title'>".($pkValue===null ? Common::t('new item', array('title'=>$this->title)) : $this->title)."</div>";
        $html .= $this->form->getHtml();       
        if($this->allowUpdate) {
            $html .= "<div class='buttons'><input type='submit' value='".Common::t('save')."'></div>";                                            
        }
        $html .= "</form>";        
        
        return $html;
    }
    
    public function validate() {   
        $valid = true;        
        foreach ($this->form->elementSet->getControls() as $control) {
            if($control->isNullPost()) {
                continue;
            }
            
            $control->validate();
            if(!$control->isValid()) {                
                $valid = false;                
            }
        }            
        return $valid;
    }    
    
    public function getFieldsErrors() {
        $errors = array();
        foreach ($this->form->elementSet->getControls() as $control) {            
            if(!$control->isValid()) {
                $errors[$control->dbName] = $control->getErrorMessage();
            }
        }         
        return $errors;
    }
    
    
    public function insert($parentNode) {   
        if(!$this->allowInsert) {
            throw new \Exception("INSERT is not allowed");
        }
        
        $params = array();
        
        if($parentNode === null) {
            $fieldsSql = "";
            $valuesSql = "";            
        }
        else {
            $fkName = null;
            foreach($parentNode->getEntity()->entities as $e) {
                if($e['entity']->name === $this->name) {
                    $fkName = $e['fkName'];
                    break;
                }
            }
            if($fkName === null) {
                throw new \Exception("Can not find FK for entity '".$this->name."'");
            }
            
            $fieldsSql = $this->tree->db->nameEscape($fkName);
            $valuesSql = $this->tree->db->escape($parentNode->getPkValue());                        
        }
        
        foreach ($this->form->elementSet->getControls() as $n=>$control) {
            $sqlPairs = $control->insert();
            if($sqlPairs !== null) {
                foreach($sqlPairs as $fieldName => $fieldValue) {
                    if($fieldsSql != "") {
                        $fieldsSql .= ", ";
                        $valuesSql .= ", ";
                    }
                    
                    $fieldsSql .= $this->tree->db->nameEscape($fieldName);
                    $valuesSql .= "?f_{$fieldName}";  
                    
                    $params["f_{$fieldName}"] = $fieldValue;
                }
            }
        }        
        
        $this->tree->db->exec("INSERT INTO ?table:name({$fieldsSql}) VALUES({$valuesSql})", array(
            'table'     => $this->table,
            'pkName'    => $this->pk,            
            'fieldsSql' => $fieldsSql,
            'valuesSql' => $valuesSql
        ) + $params);                    
        $newPkValue = $this->tree->db->getInsertId();        
        if($newPkValue === null) { // For Oracle :-(
            $newPkValue = $this->tree->db->selectVal("SELECT ?pkName:name FROM ?table:name ORDER BY ?pkName:name DESC", array(
                'table'     => $this->table,
                'pkName'    => $this->pk,
                'limit'     => 1
            ));
        }
        return $newPkValue;
    }
    
    public function update($pkValue) {          
        if(!$this->allowUpdate) {
            throw new \Exception("UPDATE is not allowed");
        }        
        
        $record = $this->tree->db->selectRow("SELECT * FROM ?table:name WHERE ?pkName:name = ?pkValue:int", array(
            'table'   => $this->table,
            'pkName'  => $this->pk,
            'pkValue' => $pkValue)
        );            
        $this->form->setValuesFromRecord($record);         
        
        
        $sqlSet = "";
        $params = array();
        foreach ($this->form->elementSet->getControls() as $n=>$control) {
            $sqlPairs = $control->update();
            if($sqlPairs !== null) {
                foreach($sqlPairs as $fieldName => $fieldValue) {
                    if($sqlSet != "") {
                        $sqlSet .= ", ";
                    }
                    $sqlSet .= $this->tree->db->nameEscape($fieldName)."=?f_{$fieldName}";
                    $params["f_{$fieldName}"] = $fieldValue;
                }
            }
        }        

        $affectedRows = $this->tree->db->exec("UPDATE ?table:name SET {$sqlSet} WHERE ?pkName:name = ?pkValue", array(
            'table'   => $this->table,
            'pkName'  => $this->pk,
            'pkValue' => $pkValue,
            'sqlSet'  => $sqlSet
        )+$params);           

        return $affectedRows;
    }
    
    public function delete($pkValue) {  
        if(!$this->allowDelete) {
            throw new \Exception("DELETE is not allowed");
        }            
        
        foreach ($this->form->elementSet->getControls() as $control) {
            $control->delete();
        }        

        $affectedRows = $this->tree->db->exec("DELETE FROM ?table:name WHERE ?pkName:name = ?pkValue", array(
            'table'   => $this->table,
            'pkName'  => $this->pk,
            'pkValue' => $pkValue            
        ));                                        
        return $affectedRows;
    }
    
    public function getNestedRecordsNum($pkValue) {  
        foreach($this->entities as $e) {
            $count = $this->tree->db->selectVal("SELECT COUNT(*) FROM ?table:name WHERE ?fkName:name = ?fkValue", array(
                'table'   => $e['entity']->table,
                'fkName'  => $e['fkName'],
                'fkValue' => $pkValue            
            ));                                        
            if($count > 0) {
                return $count;
            }
        }        
        return false;
    }    

    public function getDbStructure() {
        $struct = array(
            'name'  => $this->table,
            'pk'    => $this->pk,
            'fkes'  => array(),
            'fields'=> array()            
        );
        
        foreach($this->fkes as $fkName) {
            $struct['fkes'][] = array(
                'name' => $fkName
            );
        }
        
        foreach($this->form->elementSet->getControls() as $control) {
            foreach ($control->getDdlFields() as $dbName => $field) {
                $struct['fields'][] = array(
                    'name' => $dbName,
                    'type' => $field,
                    'nullable' => ($control->nullable == true)                
                );
            }
        }               
        
        return $struct;
    }
    
    
    public function makeBreadcrumbsItemHtml($record, $pkValue) {
        $html = '';
        foreach($this->columns as $name => $itemField) {
            if($html != '') { 
                if($this->showGrid) {
                    $html .=  "<span class='colSep'> | </span>";             
                }
            }
            $itemField->setValueFromRecord($record);
            $html .= $itemField->getHtml();                        
        }
        return $html;        
    }    
    
    public function getRowByPk($pkValue) {
        return $this->tree->db->selectRow("SELECT * FROM ?table:name WHERE ?pkName:name = ?pkValue", array(
            'table'     => $this->table,
            'pkName'    => $this->pk,
            'pkValue'   => $pkValue
        ));    
    }
    
    public function getName() {
        return $this->name;
    }

    public function addFake($fkName, $fkValue) {        
        $controls = $this->form->elementSet->getControls();
    
         
        
        for($i=0; $i<$this->fakeRecordsBatchSize; $i++) {
            $fieldsSql = '';
            $valuesSql = '';
            
            $fieldsNum = 0;
            foreach($controls as $control) {
                $fake = $control->getFake();
                if($fake) {
                    foreach($fake as $n=>$v) {
                        if($fieldsNum > 0) {
                            $fieldsSql .= ', ';
                            $valuesSql .= ', ';
                        }
                        
                        $fieldsSql .= $this->tree->db->nameEscape($n);
                        $valuesSql .= $v;                        
                        
                        $fieldsNum++;
                    }
                }
            }
            
            if($fkName !== null && $fkValue !== null) {
                $fieldsSql .= ", " . $this->tree->db->nameEscape($fkName);
                $valuesSql .= ", '" . $this->tree->db->escape($fkValue)."'";
            }
            
            if($fieldsNum > 0) {
                $this->tree->db->exec("INSERT INTO ".$this->tree->db->nameEscape($this->table)."({$fieldsSql}) VALUES({$valuesSql})");                
            }
            else {
                return 0;
            }            
        }
    
        return $this->fakeRecordsBatchSize;
    }    
    
}